﻿
using Boilen.Primitives.Members;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;


namespace Boilen.Primitives.Implementers {

    /// <inheritdoc/>
    /// <summary>
    /// Implements a mutable property.
    /// </summary>
    public class MutableProperty<T> : Property<T>, PropertyNameConstants.ITarget, NotifyPropertyChangedInterface.ITarget {

        internal const string DescriptionFormat = "Gets or sets {0}.";


        /// <summary>
        /// Gets or sets the accessibility of the property setter accessor.
        /// Any value other than <see cref="Accessibility.Public"/> will create a read-only property.
        /// </summary>
        public Accessibility SetterAccessibility { get; set; }

        /// <summary>
        /// Gets or sets the format used to compare the current and assigned values in the property setter accessor,
        ///  or null to disable equality checking.
        /// </summary>
        public string EqualityFormat { get; set; }


        /// <inheritdoc/>
        protected override IEnumerable<InitializationMember> Initializers {
            get {
                // Default value initializer.
                foreach( var condition in this.DefaultValues.Keys ) {
                    string defaultValue = this.DefaultValues[condition];
                    yield return new InitializationMember( this.FieldName, defaultValue ) { Condition = condition };
                }
            }
        }

        /// <inheritdoc/>
        protected override AccessorMember Accessor {
            get {
                var coerceHelper = new MethodMember( this.CoerceAction, "void", null ) { Parameters = { new ParameterMember( "value", "ref " + this.FieldTypeName ) }, Attributes = { AttributeMember.SuppressUnusedPartialParameters } };
                var changingHelper = new MethodMember( this.ChangingAction, "void", null ) { Parameters = { new ParameterMember( "newValue", this.FieldTypeName ) }, Attributes = { AttributeMember.SuppressUnusedPartialParameters } };
                var changedHelper = new MethodMember( this.ChangedAction, "void", null ) { Parameters = { new ParameterMember( "oldValue", this.FieldTypeName ) }, Attributes = { AttributeMember.SuppressUnusedPartialParameters } };

                bool isExternallyReadOnly = this.SetterAccessibility > this.Accessibility;
                string updateAccessibility = isExternallyReadOnly ? GetAccessibilityValue( this.SetterAccessibility ) + " " : "";
                var updateBlock = this.CreateAccessorBlock( updateAccessibility + UpdateAccessorName, this.WriteAssignmentBody );
                updateBlock.PreContent = w => w.WriteLine( "this.{0}(ref value);", this.CoerceAction );
                updateBlock.AddGuards( Guard.ChangeValueName( this.Guards, "value" ) );

                return new AccessorMember( this.AccessorName, this.TypeName ) {
                    Doc = this.CreateDoc( )
                        .AddSummary( isExternallyReadOnly ? ImmutableProperty<T>.DescriptionFormat : DescriptionFormat, this.Description ),
                    Modifiers = this.AccessorModifiers,
                    ObserveMember = this.CreateAccessorBlock(
                        ObserveAccessorName,
                        string.Format( "return this.{0};", this.FieldName )
                    ),
                    UpdateMember = updateBlock,
                    Helpers = { coerceHelper, changingHelper, changedHelper }
                };
            }
        }


        /// <inheritdoc/>
        public MutableProperty( PartialType parent, string name, string description )
            : base( parent, name, description ) {
            this.EqualityFormat = "EqualityComparer<{0}>.Default.Equals(this.{1}, value)";
        }


        /// <summary>
        /// Assigns the value used for the <see cref="SetterAccessibility"/> property.
        /// </summary>
        public MutableProperty<T> SetSetterAccessibility( Accessibility setterAccessibility ) {
            this.SetterAccessibility = setterAccessibility;
            return this;
        }

        /// <summary>
        /// Assigns the value used for the <see cref="EqualityFormat"/> property.
        /// </summary>
        [DefaultValue( "EqualityComparer<{0}>.Default.Equals(this.{1}, value)" )]
        public MutableProperty<T> SetEqualityFormat( string equalityFormat ) {
            this.EqualityFormat = equalityFormat;
            return this;
        }

        public override void Prepare( ) {
            base.Prepare( );

            // Ensure EqualityComparer type is registered, if needed.
            if( !string.IsNullOrEmpty( this.EqualityFormat ) )
                this.Parent.TypeRepository.AddNamespace( typeof( EqualityComparer<> ) );
        }


        private void WriteAssignmentBody( ICodeWriter writer ) {
            if( string.IsNullOrEmpty( this.EqualityFormat ) ) {
                this.WriteAssignmentInnerBody( writer );
            }
            else {
                writer.WriteLine( "if (!" + this.EqualityFormat + ")", this.TypeName, this.FieldName );
                using( Enclose.Braces( writer ) )
                    this.WriteAssignmentInnerBody( writer );
            }
        }

        private void WriteAssignmentInnerBody( ICodeWriter writer ) {
            writer.WriteLine( "this.{0}(value);", this.ChangingAction );
            writer.WriteLine( "{0} oldValue = this.{1};", this.FieldTypeName, this.FieldName );
            writer.WriteLine( "this.{0} = value;", this.FieldName );
            writer.Write( "this.{0}(oldValue);", this.ChangedAction );

            if( this.ImplementINotifyPropertyChanged ) {
                var conditioanlSymbols = this.conditionalINotifyPropertyChanged_
                    .Where( c => !c.IsUnconditional )
                    .Select( c => c.Symbol );
                string condition = Util.Join( conditioanlSymbols, " || " );
                bool hasCondition = !string.IsNullOrEmpty( condition );
                string onCall = string.Format( "this.OnPropertyChanged({0}{1});", this.AccessorName, PropertyNameConstants.PropertyNameSuffix );

                if( hasCondition ) {
                    writer.WriteLineUnindented( "#if {0}", condition );
                    writer.Write( onCall );
                    writer.WriteLineUnindented( "#endif" );
                }
                else {
                    writer.WriteLine( );
                    writer.WriteLine( onCall );
                }
            }
            else {
                writer.WriteLine( );
            }
        }


        #region PropertyNameConstants.ITarget Members

        /// <inheritdoc/>
        bool PropertyNameConstants.ITarget.ShouldCreateConstant {
            get { return true; }
        }

        /// <inheritdoc/>
        string PropertyNameConstants.ITarget.PropertyName {
            get { return this.AccessorName; }
        }

        /// <inheritdoc/>
        string PropertyNameConstants.ITarget.Accessibility {
            get { return GetAccessibilityValue( this.Accessibility ); }
        }

        /// <inheritdoc/>
        void PropertyNameConstants.ITarget.Prepare( PropertyNameConstants implementer ) { }

        #endregion

        #region NotifyPropertyChangedInterface.ITarget Members

        private readonly List<CompilationSymbol> conditionalINotifyPropertyChanged_ = new List<CompilationSymbol>( );

        private bool ImplementINotifyPropertyChanged { get { return this.conditionalINotifyPropertyChanged_.Any( ); } }

        /// <inheritdoc/>
        bool NotifyPropertyChangedInterface.ITarget.ShouldNotify {
            get { return true; }
        }

        /// <inheritdoc/>
        void NotifyPropertyChangedInterface.ITarget.Prepare( NotifyPropertyChangedInterface implementer ) {
            this.conditionalINotifyPropertyChanged_.Add( implementer.Condition );
        }

        #endregion

    }

}
